package cn.tedu.charging.device.startup;

import cn.tedu.charging.common.utils.JsonUtils;
import cn.tedu.charging.device.constant.DeviceCacheKey;
import cn.tedu.charging.device.dao.repository.StationRepository;
import cn.tedu.charging.device.pojo.po.StationPO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.data.geo.Point;
import org.springframework.data.redis.core.*;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.annotation.PostConstruct;
import java.io.Serializable;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 缓存预热 预热场站信息
 * 场站的基本信息
 * 场站的位置信息
 */
@Slf4j
@Component
public class StationDataInit implements ApplicationRunner {

    @PostConstruct
    public void init() {
        log.debug("@PostConstruct StationDataInit...");
    }

    @Autowired
    private StationRepository stationRepository;

    @Autowired
    private RedisTemplate<String, Serializable> redisTemplate;


    @Override
    public void run(ApplicationArguments args) throws Exception {
        log.debug("ApplicationRunner#run StationDataInit...");
        log.debug("缓存预热,在项目启动的时从数据库查询场站位置信息(经纬度),放到Redis里");
        //在预热的时候要考虑数据库表的行数,数据量,如果数据量过大,如何定义过大,数据占用的内存空间,查询的性能,考虑分页查询
        //1 如果数量过大,内存放不下,内存溢出 OutMemoryError  https://blog.csdn.net/TreeShu321/article/details/135255821
        //mysql 分页怎么分 ?  limit 两个参数 1 从哪开始 2 每页多少条数据  一个参数的时候 表示从0 开始 参数表示查多少条
        //2 ApplicationRunner#run 每次启动的时候,都会走 run 这个方法,都会从数据库查数据,放数据到Redis
        //可以先去Redis查询 看有没有数据,
        // (通过标识为 设置一个key 如果这个key value 1或者true 表示预热过了,
        // key 不存在或者 value 等于 0 不存在 表示没有预热)检查数据是否存在
        // 预热过,如果预热过,就不预热了,如果没有预热,就从数据库查询,然后放到redis 进行预热
        //3
        List<StationPO> stationPOS =  stationRepository.getAllStation();
        log.debug("数据库查询场站位置信息成功:{}",stationPOS);
        //boundGeoOps 需要指定key Geo 表示能操作Redis GEO类型的数据
        BoundGeoOperations stations = redisTemplate.boundGeoOps(DeviceCacheKey.STATION_LOCATION_KEY);
        //opsForValue 不需要指定key Value 表示能操作Redis String类型的数据
        ValueOperations<String, Serializable> valueOperations = redisTemplate.opsForValue();
        //opsForHash 不需要指定key Hash 表示能操作Redis Hash类型的数据
        HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();

        //GeoOperations<String, Serializable> geoOperations = redisTemplate.opsForGeo();
        //循环遍历从数据库查出的场站信息,放到map key 场站id value 场站经纬度 对应的是RedisGEO Point
        //循环完毕后,直接保存map,类似批处理. 批量保存,一起保存,不是遍历的保存,从而提高性能
        //放到map key 场站id value Point
        Map<String,Point> pointMap = new HashMap<>();

        if (!CollectionUtils.isEmpty(stationPOS)) {
            //循环场站列表
            for (StationPO stationPO : stationPOS) {
                //获取经度
                BigDecimal stationLng = stationPO.getStationLng();
                //获取维度
                BigDecimal stationLat = stationPO.getStationLat();
                double doubleLng = Double.parseDouble(stationLng.toEngineeringString());
                double doubleLat = Double.parseDouble(stationLat.toEngineeringString());
                //通过经度和维度构建 RedisGEO point
                Point point = new Point(doubleLng, doubleLat);
                //放到map 做批量保存到redis 准备
                log.debug("通过RedisGEO 保存场站位置信息:key:{},value:{}",stationPO.getId(),point);
                pointMap.put(String.valueOf(stationPO.getId()),point);
                //GEO 存的是场站的位置信息 经纬度 和 id
                // 把场站的详情(地址 充电站名称, 设备数量 ...) 也需要放入到redis , 用什么Redis数据类型? 数据是如何用的,
                // 如何存  key 场站id ,value 场站详情 {name:"高级充电站",桩数量:100,枪数量:200}
                // 如何取  通过站id 取 场站详情  {name:"高级充电站",桩数量:100,枪数量:200}
                // 如何修改 修改桩数量或者枪数量  取处理,改完,放进去 可以

                //String key 场站id ,value 场站详情 {name:"高级充电站",桩数量:100,枪数量:200}
                //Hash  Map<String,Map<String,value>>
                // key 场站id
                //  Map<String,value>    key name value  高级充电站  key 桩数量 value 100  key   枪数量 value 200
                //外面的key 场站id, 里面的key 桩数量  修改值



                //Redis 基本类型 String List Hash Set Zest
                //基本类型 表示的是什么呢? 表示的是 Redis 里的value , K-V结构 基本类型表示的是V  Key是String
                //String value是String                            Map<String,String>   可以 key 场站id ,value 场站详情
                //List  value是List Map套List 可以放重复的, 下标    Map<String,List<>>
                //Hash  value是Map  Map套Map  可以放重复的         Map<String,Map<String,value>>
                //Set   value是Set  Map套Set  不能重复的           Map<String,Set<>>
                //Zet   value是set  Map套Set  不能重复的,还能排序   Map<String,TreeSet<>>

                //Set Map<String,Set<>> key 站id ,value 详情


                //通过Redis String 类型保存充电站的详情
                String stationInfoStringKey = DeviceCacheKey.STATION_DETAIL_STRING_KEY + stationPO.getId();
                String json = JsonUtils.toJson(stationPO);
                valueOperations.set(stationInfoStringKey,json);
                log.debug("通过Redis基本类型String 保存场站详情:key:{},value:{}",stationInfoStringKey,json);

                //通过Redis Hash 类型保存充电站的详情
                // Map<String,Map<String,value>> 外面map的key  stationInfoHashKey value 是 smallMap
                // smallMap key value 场站具体的信息,把每个字段都作为smallMap的key,字段的值作为 smallMap的value
                String stationInfoHashKey = DeviceCacheKey.STATION_DETAIL_HASH_KEY  + stationPO.getId();
                Map<String,Object> smallMap = new HashMap<>();
                smallMap.put("name",stationPO.getStationName());
                smallMap.put("deviceNumber",stationPO.getDeviceNumber());
                smallMap.put("operatorId",stationPO.getOperatorId());
                hashOperations.putAll(stationInfoHashKey,smallMap);
                log.debug("通过Redis基本类型Hash 保存场站详情:key:{},value:{}",stationInfoHashKey,smallMap);
            /*    Map<String, Set<StationPO>> map = new  HashMap<String,Set<StationPO>>();

                HashSet<StationPO> set = new HashSet<>();
                set.add(stationPO);
                map.put(String.valueOf(stationPO.getId()),set);
                Set<StationPO> stationPOS1 = map.get(String.valueOf(stationPO.getId()));*/
            }
            //保存所有的充电站位置信息map到RedisGEO
            stations.add(pointMap);
        }
        log.debug("缓存预热完成,成功将场站的位置信息放入RedisGEO,场站基本信息放入Redis-String,场站基本信息放入Redis-Hash,体会不同类型的用法");
    }
}
